/**
 * Title:		KFrogInterpreter.java
 * Description: This is the interpreter class. It provides all the walker
 *              functionality and serves as the glue between the front-end and
 *              the back-end.
 * 
 * @version		1.0 May 2009
 * @author		Rami Habas
 */

package kfrog;

import java.util.*;
import antlr.RecognitionException;
import antlr.collections.AST;
import kfrog.type.*;

public class KFrogInterpreter {
	
	/** control flow flag values */
	final static int CONTROL_FLOW_NONE		= 0;
	final static int CONTROL_FLOW_BREAK		= 1;
	final static int CONTROL_FLOW_CONTINUE	= 2;
	final static int CONTROL_FLOW_RETURN	= 3;

	/** vector of frog objects */
	Vector<Frog> frogs;
	
	/** a canvas container object for all the graphics */
	Pond pond;
	
	/** table of all functions */
	private Hashtable<String,KFrogFunction> functions;
	
	/** symbol table for different scope variables */
	private KFrogSymbolTable symTable;
	
	/** control flow marker */
	private int controlFlow;

	/**
     * Constructor initialization
     */
	KFrogInterpreter() {
		frogs = new Vector<Frog>();
		pond = null;
		symTable  = new KFrogSymbolTable(null, null);
		functions   = new Hashtable<String,KFrogFunction>();
		controlFlow    = CONTROL_FLOW_NONE;
		KFrogBuiltInFunctions.register(functions);
	}

	/** set the current frog to inactive when exiting its scope */
	public void setFrogInactive() {
		Frog lastFrog = frogs.lastElement();
		lastFrog.setInactive();
	}

	/** start all frog threads concurrently */
	public void activateFrogs() {
		int size = frogs.size();
		for (int i = 0; i < size; i++) {
			Frog frogObj = frogs.elementAt(i);
			Thread  frog = new Thread(frogObj);
			frog.start();
		}
	}

	/** creates a frog object */
	public void createFrog()throws RuntimeException {
		if(pond == null)
			throw new RuntimeException("Illegal statement: the Pond has not" +
			" been initialized");

		Frog frog = new Frog(pond);
		frogs.add(frog);
	}

	/** sets the color of the frog */
	public void setFrogColor(String color)throws RuntimeException {
		String colorUpperCase = color.toUpperCase();
		validateIfColor(colorUpperCase);
		Frog lastFrog = getFrogIfActive();
		lastFrog.frogcolor(colorUpperCase);
	}

	public void startFrogDraw()throws NoSuchElementException {
		Frog lastFrog = getFrogIfActive();
		lastFrog.startdraw();
	}

	public void stopFrogDraw()throws NoSuchElementException {
		Frog lastFrog = getFrogIfActive();
		lastFrog.stopdraw();
	}
	
	/** Sets the speed of the frog. Only KFrogDecimal type is allowed. */
	public void setFrogSpeed(KFrogDataType speed)throws NoSuchElementException {
		int frogSpeed = 0;
		if ((speed instanceof KFrogDecimal))
			frogSpeed = ((KFrogDecimal)speed).getVariable();
		else
			throw new RuntimeException("Illegal type: expression in " +
			"speed statement must evaluate to a decimal value");

		validateSpeed(frogSpeed);
		Frog lastFrog = getFrogIfActive();
		lastFrog.speed(frogSpeed);
	}

	/**
     * Sets the frog to sleep for the given amount of time.
     * 
     * @param time         Time is in milliseconds
     */
	public void setFrogSleep(KFrogDataType time)throws NoSuchElementException {
		int frogSleepMillisecs = 0;
		if ((time instanceof KFrogDecimal))
			frogSleepMillisecs = ((KFrogDecimal)time).getVariable();
		else
			throw new RuntimeException("Illegal type: expression in " +
			"sleep statement must evaluate to a decimal value");

		Frog lastFrog = getFrogIfActive();
		lastFrog.sleep(frogSleepMillisecs);
	}
	
	public void setFrogCenter()throws NoSuchElementException {
		Frog lastFrog = getFrogIfActive();
		lastFrog.center();
	}

	/**
     * Positions the frog at the given x,y coordinate.
     * 
     * @param x         The x-coordinate must be KFrogDecimal type
     * @param y         The y-coordinate must be KFrogDecimal type
     */
	public void setFrogXY(KFrogDataType x, KFrogDataType y)
	throws NoSuchElementException {
		int xLocation = 0, yLocation = 0;
		if ((x instanceof KFrogDecimal))
			xLocation = ((KFrogDecimal)x).getVariable();
		else
			throw new RuntimeException("Illegal type: goto x-coordinate" +
			" must evaluate to decimal");

		if ((y instanceof KFrogDecimal))
			yLocation = ((KFrogDecimal)y).getVariable();
		else
			throw new RuntimeException("Illegal type: goto y-coordinate" +
			" must evaluate to decimal");

		Frog lastFrog = getFrogIfActive();
		lastFrog.move(xLocation, yLocation);
	}

	/**
     * Moves the frog forward for the specified distance.
     * 
     * @param distance         The given distance to be traversed
     */
	public void moveFrogForward(KFrogDataType distance)throws RuntimeException {
		double fwDistance = 0;
		if ((distance instanceof KFrogDecimal))
			fwDistance = ((KFrogDecimal)distance).getVariable();
		else if((distance instanceof KFrogReal))
			fwDistance = ((KFrogReal)distance).getVariable();
		else
			throw new RuntimeException("Illegal type: expression in " +
			"forward statement must evaluate to decimal or real");

		Frog lastFrog = getFrogIfActive();
		lastFrog.forward(fwDistance);
	}

	/**
     * Turns the frog to the right from its current position for the
     * given angle.
     * 
     * @param a         The angle is in degrees.
     */
	public void turnFrogRight(KFrogDataType a)throws RuntimeException {
		double angle = 0;
		if ((a instanceof KFrogDecimal))
			angle = ((KFrogDecimal)a).getVariable();
		else if((a instanceof KFrogReal))
			angle = ((KFrogReal)a).getVariable();
		else
			throw new RuntimeException("Illegal type: expression in " +
			"forward statement must evaluate to decimal or real");

		Frog lastFrog = getFrogIfActive();
		lastFrog.turnright(angle);
	}

	/**
     * Turns the frog to the left from its current position for the
     * given angle.
     * 
     * @param a         The angle is in degrees.
     */
	public void turnFrogLeft(KFrogDataType a)throws RuntimeException {
		double angle = 0;
		if ((a instanceof KFrogDecimal))
			angle = ((KFrogDecimal)a).getVariable();
		else if((a instanceof KFrogReal))
			angle = ((KFrogReal)a).getVariable();
		else
			throw new RuntimeException("Illegal type: expression in " +
			"forward statement must evaluate to decimal or real");

		Frog lastFrog = getFrogIfActive();
		lastFrog.turnleft(angle);
	}

	/**
     * Directs the frog to move to the right along a circular arc with
     * a given radius and subtending a given angle.
     * 
     * @param r         The radius of the arc.
     * @param a         The angle is in degrees.
     */
	public void curveFrogRight(KFrogDataType r, KFrogDataType a)
	throws RuntimeException {
		double angle = 0, radius = 0;

		if ((r instanceof KFrogDecimal))
			radius = ((KFrogDecimal)r).getVariable();
		else if((r instanceof KFrogReal))
			radius = ((KFrogReal)r).getVariable();
		else
			throw new RuntimeException("Illegal type: radius expression in " +
			"curve statement must evaluate to decimal or real");

		if ((a instanceof KFrogDecimal))
			angle = ((KFrogDecimal)a).getVariable();
		else if((a instanceof KFrogReal))
			angle = ((KFrogReal)a).getVariable();
		else
			throw new RuntimeException("Illegal type: angle expression in " +
			"curve statement must evaluate to decimal or real");

		Frog lastFrog = getFrogIfActive();
		lastFrog.rightcurve(radius, angle);
	}
	
	/**
     * Directs the frog to move to the left along a circular arc with
     * a given radius and subtending a given angle.
     * 
     * @param r         The radius of the arc.
     * @param a         The angle is in degrees.
     */
	public void curveFrogLeft(KFrogDataType r, KFrogDataType a)
	throws RuntimeException {
		double angle = 0, radius = 0;

		if ((r instanceof KFrogDecimal))
			radius = ((KFrogDecimal)r).getVariable();
		else if((r instanceof KFrogReal))
			radius = ((KFrogReal)r).getVariable();
		else
			throw new RuntimeException("Illegal type: radius expression in " +
			"curve statement must evaluate to decimal or real");

		if ((a instanceof KFrogDecimal))
			angle = ((KFrogDecimal)a).getVariable();
		else if((a instanceof KFrogReal))
			angle = ((KFrogReal)a).getVariable();
		else
			throw new RuntimeException("Illegal type: angle expression in " +
			"curve statement must evaluate to decimal or real");

		Frog lastFrog = getFrogIfActive();
		lastFrog.leftcurve(radius, angle);
	}

	/**
     * Create a canvas with the given dimensions.
     * 
     * @param w         The width of the canvas must be of KFrogDecimal type 
     * @param h         The height of the canvas must be of KFrogDecimal type 
     */
	public void createPond(KFrogDataType w, KFrogDataType h) {
		int width = 0, height = 0;
		if ((w instanceof KFrogDecimal))
			width = ((KFrogDecimal)w).getVariable();
		else
			throw new RuntimeException("Illegal type: pondsize width " +
			"must evaluate to decimal");

		if ((h instanceof KFrogDecimal))
			height = ((KFrogDecimal)h).getVariable();
		else
			throw new RuntimeException("Illegal type: pondsize height " +
			"must evaluate to decimal");

		pond = new Pond(width, height);
	}

	public void setPondColor(String color)throws RuntimeException {
		String colorUpperCase = color.toUpperCase();
		validateIfColor(colorUpperCase);
		pond.setPondColor(colorUpperCase);
	}

	public void clearPond()throws RuntimeException {
		if(pond == null)
			throw new RuntimeException("Illegal statement: the Pond has not" +
			" been initialized");
		pond.clearscreen();
	}

	public void resetPond()throws RuntimeException {
		if(pond == null)
			throw new RuntimeException("Illegal statement: the Pond has not" +
			" been initialized");
		pond.reset();
	}

	/** returns the last active frog */
	private Frog getFrogIfActive()throws RuntimeException {
		Frog lastFrog = frogs.lastElement();
		if(!lastFrog.isActive())
			throw new RuntimeException("Illegal command statement: Frog " +
			"command statements must be called within the scope of a frog");
		return lastFrog;
	}

	public void setBreak(){controlFlow = CONTROL_FLOW_BREAK;}

	public void setContinue(){controlFlow = CONTROL_FLOW_CONTINUE;}

	public void setReturn(){controlFlow = CONTROL_FLOW_RETURN;}

	public void handleBreak() {
		if (controlFlow == CONTROL_FLOW_BREAK)
			controlFlow = CONTROL_FLOW_NONE;
	}

	public void handleContinue() {
		if (controlFlow == CONTROL_FLOW_CONTINUE)
			controlFlow = CONTROL_FLOW_NONE;
	}

	public void handleReturn() {
		if (controlFlow == CONTROL_FLOW_RETURN)
			controlFlow = CONTROL_FLOW_NONE;
	}

	public boolean mayProceed() {
		return controlFlow == CONTROL_FLOW_NONE;
	}

	public boolean loopMayProceed(KFrogAntlrWalker walker, AST condition)
	throws RecognitionException {
		if (controlFlow != CONTROL_FLOW_NONE)
			return false;

		KFrogDataType result = walker.expression(condition);
		if (!(result instanceof KFrogLogical))
			throw new RuntimeException("Illegal expression: loop condition must"
					+ " evaluate to logical");
		return ((KFrogLogical)result).getVariable();
	}

	/**
     * Repeats the body of the loop a given number of times. The walker is used
     * to walk the AST subtree that corresponds to the loop's body.
     * 
     * @param walker        The walker
     * @param iterator      The number of iterations
     * @param body          The body of the repeat loop
     */
	public void executeRepeatLoop(KFrogAntlrWalker walker, 
			KFrogDataType iterator, AST body)
	throws RuntimeException, RecognitionException {
		int count;
		if (!(iterator instanceof KFrogDecimal))
			throw new RuntimeException("Illegal type: number of iterations" +
			" in a repeat statement must evaluate to decimal");
		count = ((KFrogDecimal)iterator).getVariable();
		if(count <= 0)
			throw new RuntimeException("Illegal value: number of iterations" +
			" in a repeat statement must be > 0");

		validateIfStmt(body);
		for(int i = 0; i < count && mayProceed(); i++){
			walker.statement(body);
			handleContinue();
		}
		handleBreak();
	}

	/**
     * Invokes the function with the given name and passing the given actual
     * parameters. The function executes the computation and returns the result.
     * 
     * @param walker       The walker
     * @param parameters   The actual parameters
     * @param name         The function name
     */
	public KFrogDataType invokeFunction(KFrogAntlrWalker walker, 
			KFrogDataType[] parameters, String name)
	throws RecognitionException {
		KFrogFunction function;

		//check by signature
		String signature = KFrogFunction.getSignature(name, parameters);
		function = (KFrogFunction)functions.get(signature);


		//function was not found
		if (function == null)
			throw new RuntimeException("Illegal function call: " + signature +
			" has not been defined");

		//if it is a built-in function call and return
		if (function.isBuiltIn())
			return KFrogBuiltInFunctions.call(function, parameters);

		//enter a new scope
		symTable = new KFrogSymbolTable(function.getStaticParent(), symTable);
		if (KFrogMain.debug)
			System.out.println("function call: " + signature + " {}");

		//assign the parameters to arguments of the callee
		KFrogDataType[] args = function.getArgs();
		String argName;
		for (int i = 0; i < parameters.length; i++){
			argName = args[i].getName();
			args[i].assign(parameters[i]);
			args[i].setName(argName);
			symTable.put(argName, args[i]);
		}

		//walk the body of the function
		KFrogDataType result = walker.statement(function.getBody());

		//continue and break are unexpected
		if (controlFlow == CONTROL_FLOW_CONTINUE)
			throw new RuntimeException("unexpected continue");
		if (controlFlow == CONTROL_FLOW_BREAK)
			throw new RuntimeException("unexpected break");

		//handle return from funciton
		handleReturn();

		//return type of result must be the same as the function return type
		if (result instanceof KFrogDecimal &&
				function.getReturnType() instanceof KFrogReal) {
			result = new KFrogReal(((KFrogDecimal)result).getVariable());
		} 
		else {
			if (result == null) {
				if (!function.isReturnTypeVoid())
					throw new RuntimeException("Illegal return type: " + 
							function.getSignature() + " returns " + 
							function.getReturnType() + " and result is null.");
			}
			else if (!result.typeName().equals
					(function.getReturnType().typeName()))
				throw new RuntimeException("Illegal return type: " +
						function.getSignature() + " returns " + 
						function.getReturnType() + " and result is " +
						result.typeName());
		}

		//exit the scope
		symTable = symTable.getDynamicParent();

		if (KFrogMain.debug)
			System.out.println("function exit:"+signature);

		return result;
	}
	
	/**
     * The given walker interprets the given AST subtree.
     * 
     * @param walker        The walker
     * @param block         The block to be executed
     */
	public KFrogDataType executeBlock(KFrogAntlrWalker walker, AST block)
	throws RecognitionException {
		// enter a new scope
		symTable = new KFrogSymbolTable(symTable, symTable);
		if (KFrogMain.debug)
			System.out.println("block {");

		//walk through the block
		KFrogDataType result = walker.statement(block);

		// exit the scope
		symTable = symTable.getDynamicParent();
		if (KFrogMain.debug)
			System.out.println("}");

		return result;
	}
	
	/**
     * Creates a function entry in the function table based on the given
     * function's name, arguments, body, and return type.
     * 
     * @param name         Function name
     * @param args         Function arguments
     * @param body         Function body as an AST
     * @param type         Function return type
     */
	public void registerFunction(String name, KFrogDataType[] args,
			AST body, KFrogDataType type) {
		KFrogFunction function = 
			new KFrogFunction(name, args, body, type, symTable);

		if (functions.get(function.getSignature()) != null )
			throw new RuntimeException("Illegal function definition: " +
					function.getSignature() + " is duplicate");
		functions.put(function.getSignature(), function);

		if (KFrogMain.debug)
			System.out.println("Registered function: " + function.toString());
	}

	/**
     * Creates a variable entry in the symbol table based on the given
     * variable's name, type and value.
     * 
     * @param name         Function name
     * @param args         Function arguments
     * @param body         Function body as an AST
     * @param type         Function return type
     */
	public void registerVariable(String name, KFrogDataType type,
			KFrogDataType val) {
		if (symTable.get(name) != null)
			throw new RuntimeException("Illegal variable definition: " +
					name + " is duplicate");

		KFrogDataType variable = type.newType();
		variable.setName(name);

		if (val != null)
			variable.assign(val);

		symTable.put(name, variable);

		if (KFrogMain.debug)
			System.out.println("Registered variable: " + variable.toString());
	}

	public KFrogDataType getVariable(String name) {
		KFrogDataType var;
		KFrogSymbolTable varSymTable = symTable;
		while(varSymTable != null){
			if((var = (KFrogDataType)varSymTable.get(name)) != null)
				return var;

			varSymTable = varSymTable.getStaticParent();
		}

		throw new RuntimeException("Illegal variable invocation: " +
				name + " not declared");
	}

	public static KFrogDataType rValue(KFrogDataType a) {
		if (a.getName() == null)
			return a;
		return a.copy();
	}

	public static KFrogDataType[] getArray(ArrayList<KFrogDataType> list) {
		KFrogDataType[] array = new KFrogDataType[list.size()];
		list.toArray(array);
		return array;
	}
	
	/**
	 * Validation methods used for semantics checking
	 */
	public static void validateIfStmt(AST node) {
		switch (node.getType()) {
		case KFrogAntlrWalker.NL:
		case KFrogAntlrWalker.LITERAL_new:
		case KFrogAntlrWalker.CLEAR:
		case KFrogAntlrWalker.RESET:
		case KFrogAntlrWalker.PONDSIZE:
		case KFrogAntlrWalker.PONDCOLOR:
		case KFrogAntlrWalker.FROGCOLOR:
		case KFrogAntlrWalker.STARTDRAW:
		case KFrogAntlrWalker.STOPDRAW:
		case KFrogAntlrWalker.SPEED:
		case KFrogAntlrWalker.SLEEP:
		case KFrogAntlrWalker.CENTER:
		case KFrogAntlrWalker.GOTO:
		case KFrogAntlrWalker.FORWARD:
		case KFrogAntlrWalker.TURNRIGHT:
		case KFrogAntlrWalker.TURNLEFT:
		case KFrogAntlrWalker.RIGHTCURVE:
		case KFrogAntlrWalker.LEFTCURVE:
		case KFrogAntlrWalker.LITERAL_repeat:
		case KFrogAntlrWalker.LITERAL_while:
		case KFrogAntlrWalker.LITERAL_break:
		case KFrogAntlrWalker.LITERAL_continue:
		case KFrogAntlrWalker.LITERAL_return:
		case KFrogAntlrWalker.LITERAL_if:
		case KFrogAntlrWalker.STATEMENT:
		case KFrogAntlrWalker.COMPOUND_STMT:
			return;
		}
		validateIfExpr(node);
	}

	public static void validateIfStmtOrDeclaration(AST node) {
		if (node.getType() == KFrogAntlrWalker.DECLARATION)
			return;
		validateIfStmt(node);
	}

	public static void validateIfExpr(AST node) {
		switch (node.getType()) {
		case KFrogAntlrWalker.ASSIGN:
		case KFrogAntlrWalker.MULTEQ:
		case KFrogAntlrWalker.DIVEQ:
		case KFrogAntlrWalker.PLUSEQ:
		case KFrogAntlrWalker.MINUSEQ:
		case KFrogAntlrWalker.PREF_INC:
		case KFrogAntlrWalker.PREF_DEC:
		case KFrogAntlrWalker.POST_INC:
		case KFrogAntlrWalker.POST_DEC:
		case KFrogAntlrWalker.FUNC_CALL:
			return;
		}
		throw new RuntimeException("Statement validation failed!");
	}

	public static void validateIfColor(String color) {
		if(color.equals(Const.RED)        ||
				color.equals(Const.GREEN) ||
				color.equals(Const.BLUE)  ||
				color.equals(Const.YELLOW)||
				color.equals(Const.BLACK) ||
				color.equals(Const.WHITE)
		)return;

		throw new RuntimeException("Color must be one of the following: " +
				"red, green, blue, yellow, black, or white");
	}
	
	public static void validateSpeed(int speed) {
		if(speed >= Const.MINSPEED && speed <= Const.MAXSPEED)
			return;

		throw new RuntimeException("Speed must be >= " + Const.MINSPEED +
				" and <= " + Const.MAXSPEED);
	}
}
